<?php

declare(strict_types=1);

namespace Upside\Std;

use Upside\Std\Option;
use function Upside\Std\{None, Some};

class Arr
{
    protected array $items;

    public function __construct(array $items = [])
    {
        $this->items = $items;
    }

    public static function fill(int $startIndex, int $count, mixed $value): static
    {
        return new static(\array_fill($startIndex, $count, $value));
    }

    public static function fill_keys(array $keys, mixed $value): static
    {
        return new static(\array_fill_keys($keys, $value));
    }

    public static function range(string|int|float $start, string|int|float $end, int|float $step = 1): static
    {
        return new static(\range($start, $end, $step));
    }

    public function chunk(int $length): static
    {
        $result = new static();
        if ($length <= 0) {
            return $result;
        }

        foreach (\array_chunk($this->items, $length) as $chunk) {
            $result->push(new static($chunk));
        }

        return $result;
    }

    public function push(mixed $item): self
    {
        $this->items[] = $item;

        return $this;
    }

    public function collect(): static
    {
        return new static($this->items);
    }

    public function first(): Option
    {
        if ($this->is_empty()) {
            return None();
        }

        return Some($this->items[\array_key_first($this->items)]);
    }

    public function is_empty(): bool
    {
        return $this->count() === 0;
    }

    public function count(): int
    {
        return \count($this->items);
    }

    public function last(): Option
    {
        if ($this->is_empty()) {
            return None();
        }

        return Some($this->items[\array_key_last($this->items)]);
    }

    public function pad(int $length, mixed $value): static
    {
        return new static(\array_pad($this->items, $length, $value));
    }

    public function contains(mixed $needle, bool $strict = false): bool
    {
        return \in_array($needle, $this->items, $strict);
    }

    public function has_any(array $keys): bool
    {
        foreach ($keys as $key) {
            if ($this->has($key)) {
                return true;
            }
        }

        return false;
    }

    public function has(mixed $key): bool
    {
        return \array_key_exists($key, $this->items);
    }

    public function diff(array ...$arrays): static
    {
        return new static(\array_diff($this->items, ...$arrays));
    }

    /**
     * @param callable(mixed $value, array-key $key):mixed $callable
     * @return Arr
     */
    public function map(callable $callable): static
    {
        $result = new static();
        foreach ($this->items as $key => $value) {
            $result->push($callable($value, $key));
        }

        return $result;
    }

    public function keys(): static
    {
        return new static(\array_keys($this->items));
    }

    public function combine(array $data): Arr
    {
        return new static(\array_combine($this->values()->raw(), \array_values($data)));
    }

    public function raw(): array
    {
        return $this->items;
    }

    public function values(): static
    {
        return new static(\array_values($this->items));
    }

    public function reverse(bool $preserveKeys = false): static
    {
        return new static(\array_reverse($this->items, $preserveKeys));
    }

    //TODO: Подумать над arsort uasort
    public function sort(int $flags = \SORT_REGULAR): static
    {
        $data = $this->items;
        \sort($data, $flags);

        return new static($data);
    }

    //TODO: Подумать над ksort krsort
    public function sort_keys(int $flags = \SORT_REGULAR): static
    {
        $data = $this->items;
        \ksort($data, $flags);

        return new static($data);
    }

    public function skip(int $skip): static
    {
        return $this->slice($skip);
    }

    public function slice(int $offset, ?int $length = null): static
    {
        return new static(\array_slice($this->items, $offset, $length, true));
    }
}
